﻿
using System.IO;

namespace DA6502
{
    /// <summary>
    /// NES 游戏映像文件
    /// </summary>
    public class NESImage
    {
        /// <summary>
        /// NES映像中，每个PRG-ROM块的大小固定为16k字节
        /// </summary>
        public const int PROGRAMME_ROM_CHUNK_SIZE = 16384;

        /// <summary>
        /// NES映像中，每个CHR-ROM块的大小固定为8k字节
        /// </summary>
        public const int CHARACTOR_ROM_CHUNK_SIZE = 8192;

        private byte[] _FileType = new byte[4];

        private byte _nPrgRom;

        private byte _nChrRom;

        private byte _flags6;

        private byte _flags7;

        private byte[] _reserved = new byte[8];

        private byte[] _proRom;

        private byte[] _chrRom;

        /// <summary>
        /// 映像类型字段，固定为 NES^Z
        /// </summary>
        public byte[] FileType { get => _FileType; set => _FileType = value; }

        /// <summary>
        /// 只读程序存储器的块数（每块大小为16k）
        /// </summary>
        public byte NPrgRom
        {
            get => _nPrgRom;
            set
            {
                _nPrgRom = value;
                _proRom = new byte[value * PROGRAMME_ROM_CHUNK_SIZE];
            }
        }

        /// <summary>
        /// 只读字符存储器的块数（每块大小为8k）
        /// </summary>
        public byte NChrRom
        {
            get => _nChrRom;
            set
            {
                _nChrRom = value;
                _chrRom = new byte[value * CHARACTOR_ROM_CHUNK_SIZE];
            }
        }


        /// <summary>
        /// Flags 6
        /// </summary>
        /// 7       0
        /// ---------
        /// NNNN FTBM
        /// 
        /// N: Mapper编号低4位
        /// F: 4屏标志位. (如果该位被设置, 则忽略M标志)
        /// T: Trainer标志位.  1表示 $7000-$71FF加载 Trainer
        /// B: SRAM标志位 $6000-$7FFF拥有电池供电的SRAM.
        /// M: 镜像标志位.  0 = 水平, 1 = 垂直.
        public byte Flags6 { get => _flags6; set => _flags6 = value; }

        /// <summary>
        /// Flags 7
        /// </summary>
        /// 7       0
        /// ---------
        /// NNNN xxPV
        ///     
        /// N: Mapper编号高4位
        /// P: Playchoice 10标志位.被设置则表示为PC-10游戏
        /// V: Vs.Unisystem标志位.被设置则表示为Vs.游戏
        /// x: 未使用
        public virtual byte Flags7 { get => _flags7; set => _flags7 = value; }

        /// <summary>
        /// 保留字段，未使用
        /// </summary>
        public virtual byte[] Reserved { get => _reserved; set => _reserved = value; }

        /// <summary>
        /// 只读程序存储区
        /// </summary>
        public virtual byte[] ProRom { get => _proRom; set => _proRom = value; }

        /// <summary>
        /// 只读字符存储区
        /// </summary>
        public virtual byte[] ChrRom { get => _chrRom; set => _chrRom = value; }

        /// <summary>
        /// Mapper编号
        /// </summary>
        public virtual byte Mapper
        {
            get => (byte)((Flags6 >> 4) | (Flags7 & 0xF0));
            set
            {
                Flags6 = (byte)((0x0F & Flags6) | ((value & 0x0F) << 4));
                Flags7 = (byte)((0x0F & Flags7) | (value & 0xF0));
            }
        }

        /// <summary>
        /// 4屏标志位. (如果该位被设置, 则忽略M标志)
        /// </summary>
        public virtual bool ScreenFlag
        {
            get => (Flags6 & 0x08) != 0;
            set => Flags6 = value ? (byte)(Flags6 | 0x08) : (byte)(Flags6 & 0xF7);
        }

        /// <summary>
        /// Trainer标志位.  1表示 $7000-$71FF加载 Trainer
        /// </summary>
        public virtual bool Trainerflag
        {
            get => (Flags6 & 0x04) != 0;
            set => Flags6 = value ? (byte)(Flags6 | 0x04) : (byte)(Flags6 & 0xFB);
        }

        /// <summary>
        /// SRAM标志位 $6000-$7FFF拥有电池供电的SRAM.
        /// </summary>
        public virtual bool SramFlag
        {
            get => (Flags6 & 0x02) != 0;
            set => Flags6 = value ? (byte)(Flags6 | 0x02) : (byte)(Flags6 & 0xFD);
        }

        /// <summary>
        /// 镜像标志位.  0 = 水平, 1 = 垂直.
        /// </summary>
        public virtual bool MirrorFlag
        {
            get => (Flags6 & 0x01) != 0;
            set => Flags6 = value ? (byte)(Flags6 | 0x01) : (byte)(Flags6 & 0xFE);
        }

        /// <summary>
        /// Playchoice 10标志位.被设置则表示为PC-10游戏
        /// </summary>
        public virtual bool PlayChoiceXFlag
        {
            get => (Flags7 & 0x02) != 0;
            set => Flags7 = value ? (byte)(Flags7 | 0x02) : (byte)(Flags7 & 0xFD);
        }

        /// <summary>
        /// Vs.Unisystem标志位.被设置则表示为Vs.游戏
        /// </summary>
        public virtual bool VsUnisystemFlag
        {
            get => (Flags7 & 0x01) != 0;
            set => Flags7 = value ? (byte)(Flags7 | 0x01) : (byte)(Flags7 & 0xFE);
        }

        public static NESImage ParseImageFile(string FileName)

        {
            FileStream fs = new FileStream(FileName, FileMode.Open);
            NESImage img = ParseImageFile(fs);
            fs.Close();
            return img;
        }

        public static NESImage ParseImageFile(FileStream fileStream)
        {
            NESImage img = new NESImage();
            byte[] buffer = new byte[4];

            //  File header, 8 bytes
            if (fileStream.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new FileLoadException("NES映像文件长度异常.");
            }
            buffer.CopyTo(img.FileType, 0);

            if (fileStream.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new FileLoadException("NES映像文件长度异常.");
            }
            img.NPrgRom = buffer[0];
            img.NChrRom = buffer[1];
            img.Flags6 = buffer[2];
            img.Flags7 = buffer[3];

            //  8 reserved bytes
            if (fileStream.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new FileLoadException("NES映像文件长度异常.");
            }
            if (fileStream.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new FileLoadException("NES映像文件长度异常.");
            }

            //  PRO_ROM
            for (int i = 0; i < img.NPrgRom; i++)
            {
                if (fileStream.Read(img.ProRom, i * PROGRAMME_ROM_CHUNK_SIZE, PROGRAMME_ROM_CHUNK_SIZE) != PROGRAMME_ROM_CHUNK_SIZE)
                {
                    throw new FileLoadException("NES映像文件长度异常.");
                }

            }

            // CHR_ROM
            for (int i = 0; i < img.NChrRom; i++)
            {
                if (fileStream.Read(img.ChrRom, i * CHARACTOR_ROM_CHUNK_SIZE, CHARACTOR_ROM_CHUNK_SIZE) != CHARACTOR_ROM_CHUNK_SIZE)
                {
                    throw new FileLoadException("NES映像文件长度异常.");
                }
            }

            return img;
        }
    }
}
